<?php

declare(strict_types=1);

namespace Modules\DataCheck\Excel\Export;

use Modules\DataCheck\Enums\ErrorEnum;
use Modules\DataCheck\Excel\Tratis\Excel;
use Modules\DataCheck\Exceptions\DataCheckException;
use Modules\DataCheck\Models\DcTask;
use Modules\DataCheck\Models\DcTaskTargetResult;
use Modules\DataCheck\Services\TaskProcessService;
use Vtiful\Kernel\Format;

/**
 * 导出数据
 */
class DcTaskExport
{
    use Excel;

    /**
     * 每页数量
     * @var int
     */
    private int $pageSize = 100;

    /**
     * 文件存放目录
     * @var string
     */
    private string $basePath;

    //设置excel表头
    public array $excelHead = [
        'bigHead' => [],
        'smallHead' => [
            ['bgcolor' => 0x92CDDC, 'fontSize' => 14, 'width' => 20, 'title' => '管理部门', 'field' => 'department'],
            ['bgcolor' => 0x92CDDC, 'fontSize' => 14, 'width' => 45, 'title' => '领用人', 'field' => 'org'],
            ['bgcolor' => 0x92CDDC, 'fontSize' => 14, 'width' => 15, 'title' => '单证编码', 'field' => 'code'],
            ['bgcolor' => 0x92CDDC, 'fontSize' => 14, 'width' => 35, 'title' => '单证名称', 'field' => 'name'],
            ['bgcolor' => 0x92CDDC, 'fontSize' => 14, 'width' => 25, 'title' => '开始流水号', 'field' => 'serial_num_start'],
            ['bgcolor' => 0x92CDDC, 'fontSize' => 14, 'width' => 25, 'title' => '结束流水号', 'field' => 'serial_num_end'],
            ['bgcolor' => 0x92CDDC, 'fontSize' => 14, 'width' => 10, 'title' => '数量', 'field' => 'num'],
            ['bgcolor' => 0x92CDDC, 'fontSize' => 14, 'width' => 15, 'title' => '核对状态', 'field' => 'check_state'],
        ],
    ];

    /**
     * 列样式
     * @var array|array[]
     */
    private array $columnStyle = [
        'serial_num_start' => [
            'bgcolor' => 0x00B050,
            'fontSize' => 14,
            'align' => [Format::FORMAT_ALIGN_CENTER, Format::FORMAT_ALIGN_VERTICAL_CENTER],
            'border' => \Vtiful\Kernel\Format::BORDER_THIN,
        ],
        'serial_num_end' => [
            'bgcolor' => 0x00B050,
            'fontSize' => 14,
            'align' => [Format::FORMAT_ALIGN_CENTER, Format::FORMAT_ALIGN_VERTICAL_CENTER],
            'border' => \Vtiful\Kernel\Format::BORDER_THIN,
        ],
    ];

    public function __construct()
    {
        $this->basePath = public_path();
    }

    /**
     * 导出数据
     * @param DcTask                  $task
     * @param TaskProcessService|null $taskProcessService
     * @param int                     $startProcess
     * @param int                     $endProcess
     * @return array
     */
    public function handle(
        DcTask $task,
        ?TaskProcessService $taskProcessService = null,
        int $startProcess = 0,
        int $endProcess = 100
    ): array {
        //如果有核对表名，导出的文件以核对表命令，否则按时间生成
        $targetFile = is_string($task->target_file) ? json_decode($task->target_file, true) : $task->target_file;
        if (!empty($targetFile) && !empty($targetFile['file_name'])) {
            $fileName = $targetFile['file_name'];
        } else {
            $fileName = $task->task_name . '_' . date('YmdHis') . mt_rand(100, 999) . '.xlsx';
        }

        $path = DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'excel' . DIRECTORY_SEPARATOR . date('Ym');
        $path .= DIRECTORY_SEPARATOR . $this->getRandomPath() . DIRECTORY_SEPARATOR;
        $realPath = $this->basePath . $path;
        $url = asset(str_replace(DIRECTORY_SEPARATOR, '/', $path) . $fileName);

        if (!is_dir($realPath) && !mkdir($realPath, 0777, true)) {
            throw new DataCheckException('生成缓存目录失败', ErrorEnum::DATA_CHECK);
        }

        $result = [
            'real_path' => $realPath,
            'path' => $path,
            'file_name' => $fileName,
            'url' => $url,
        ];

        //设置开始进度
        $taskProcessService?->set($startProcess);

        //进度范围
        $processRange = $endProcess - $startProcess;

        //获取数据总数
        $query = DcTaskTargetResult::query()->where('task_id', $task->task_id);
        $total = $query->count();

        //获取 sheet 列表
        $sheetList = $query->groupBy('sheet_name')->get('sheet_name');

        //处理的数量
        $count = 0;
        $source = $excelObj = null;
        foreach ($sheetList as $sheet) {
            //第一次创建excel对象
            if (is_null($source)) {
                $source = $this->createExcelT($realPath, $this->excelHead, true, $fileName, [], $sheet->sheet_name);
                $excelObj = $source['excel_obj'];
                $startLine = $source['start_line'];
            } else {
                // 添加 sheet
                $excelObj->addSheet($sheet->sheet_name);

                //设置头
                $this->setConstMemoryHeader($excelObj, $this->excelHead, false);
                $startLine = 1;
            }

            $query = DcTaskTargetResult::query()->where('task_id', $task->task_id)->where('sheet_name', $sheet->sheet_name);
            $offset = 0;
            while (true) {
                $list = $query->offset($offset)->limit($this->pageSize)->get()->toArray();
                if (empty($list)) {
                    break;
                }

                $offset += $this->pageSize;

                $pageData = [];
                foreach ($list as $item) {
                    $lineData = json_decode($item['line_data'], true);
                    $pageData[] = [
                        'department' => $lineData['department'],
                        'org' => $lineData['org'],
                        'code' => $lineData['code'],
                        'name' => $lineData['name'],
                        'serial_num_start' => (string)$item['serial_num_start'],
                        'serial_num_end' => (string)$item['serial_num_end'],
                        'num' => $lineData['num'],
                        'check_state' => $item['check_state'],
                    ];

                    $count++;
                }

                //格式化数据
                $lineData = $this->formatDataT($pageData, $this->excelHead, $startLine);

                //将数据写入excel
                $startLine = $this->setExcelDataT($excelObj, $startLine, $lineData, $this->columnStyle);

                //清空数据
                unset($lineData, $pageData);

                // 如果数据少于每页数量，则结束循环
                if (count($list) < $this->pageSize) {
                    break;
                }

                //设置进度
                $taskProcessService?->set(intval(($count / $total) * $processRange) + $startProcess);
            }
        }

        if (!is_null($source)) {
            $this->outputExcel($excelObj);
            $excelObj->close();
        }

        //设置进度
        $taskProcessService?->set($endProcess);

        return $result;
    }

    /**
     * @return string
     */
    private function getRandomPath(): string
    {
        try {
            return bin2hex(random_bytes(6));
        } catch (\Exception $exception) {
            return mb_substr(md5(microtime(true) . mt_rand(10000, 99999)), 0, 6);
        }
    }
}
